package com.arashivision.webrtc;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;

import org.webrtc.Camera1Enumerator;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.EglBase;
import org.webrtc.FileVideoCapturer;
import org.webrtc.GlRectDrawer;
import org.webrtc.IceCandidate;
import org.webrtc.Logging;
import org.webrtc.PeerConnection;
import org.webrtc.RendererCommon;
import org.webrtc.ScreenCapturerAndroid;
import org.webrtc.SessionDescription;
import org.webrtc.StatsReport;
import org.webrtc.SurfaceEglRendererNew;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoFileRenderer;
import org.webrtc.VideoFrame;
import org.webrtc.VideoRenderer;
import org.webrtc.VideoSink;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;



/**
 * Created by vans on 30/12/17.
 */

public class OneRTCImplement implements OneSignalingEvents, PeerConnectionClient.PeerConnectionEvents
{
    private static final String TAG = "OneRTC";

        // Fix for devices running old Android versions not finding the libraries.
        // https://bugs.chromium.org/p/webrtc/issues/detail?id=6751
        static {
            try {
                System.loadLibrary("c++_shared");
                System.loadLibrary("boringssl.cr");
                System.loadLibrary("protobuf_lite.cr");
            } catch (UnsatisfiedLinkError e) {
                Logging.w(TAG, "Failed to load native dependencies: ", e);
            }
        }

    public enum RTCState{
        IDLE,
        CONNECTTING,
        CONNECTED
    }

    private RTCState mRTCState = RTCState.IDLE;

        private static final int CAPTURE_PERMISSION_REQUEST_CODE = 1;

        // List of mandatory application permissions.
        private static final String[] MANDATORY_PERMISSIONS = {"android.permission.MODIFY_AUDIO_SETTINGS",
                "android.permission.RECORD_AUDIO", "android.permission.INTERNET"};

        // Peer connection statistics callback period in ms.
        private static final int STAT_CALLBACK_PERIOD = 1000;

        private static class ProxyRenderer implements VideoRenderer.Callbacks {
            private VideoRenderer.Callbacks target;

            @Override
            synchronized public void renderFrame(VideoRenderer.I420Frame frame) {
                if (target == null) {
                    Logging.d(TAG, "Dropping frame in proxy because target is null.");
                    VideoRenderer.renderFrameDone(frame);
                    return;
                }

                target.renderFrame(frame);
            }

            synchronized public void setTarget(VideoRenderer.Callbacks target) {
                this.target = target;
            }
        }

        private static class ProxyVideoSink implements VideoSink {
            private VideoSink target;

            @Override
            synchronized public void onFrame(VideoFrame frame) {
                if (target == null) {
                    Logging.d(TAG, "Dropping frame in proxy because target is null.");
                    return;
                }

                target.onFrame(frame);
            }

            synchronized public void setTarget(VideoSink target) {
                this.target = target;
            }
        }

    private final RendererCommon.VideoLayoutMeasure videoLayoutMeasure =
            new RendererCommon.VideoLayoutMeasure();
    private final SurfaceEglRendererNew pipRenderer;
    private final SurfaceEglRendererNew fullscreenRenderer;

    // Callback for reporting renderer events. Read-only after initilization so no lock required.
    private RendererCommon.RendererEvents rendererEvents;


    private final ProxyRenderer remoteProxyRenderer = new ProxyRenderer();
        private final ProxyVideoSink localProxyVideoSink = new ProxyVideoSink();
        private PeerConnectionClient peerConnectionClient = null;
        private AppRTCClient appRtcClient;
        private AppRTCClient.SignalingParameters signalingParameters;
        private AppRTCAudioManager audioManager = null;

        private VideoFileRenderer videoFileRenderer;
        private final List<VideoRenderer.Callbacks> remoteRenderers = new ArrayList<>();
        private AppRTCClient.RoomConnectionParameters roomConnectionParameters;
        private PeerConnectionClient.PeerConnectionParameters peerConnectionParameters;
        private boolean iceConnected;
        private boolean isError;
        private boolean callControlFragmentVisible = true;
        private long callStartedTimeMs = 0;
        private boolean micEnabled = true;
        private boolean screencaptureEnabled = false;
        private static Intent mediaProjectionPermissionResultData;
        private static int mediaProjectionPermissionResultCode;
        // True if local view is in the fullscreen renderer.
        private boolean isSwappedFeeds;

        private CpuMonitor cpuMonitor;
        private Handler mUIHandler;
        //handler from internal looper
        private Handler mHandler;
        private Handler mCBHandler;


        private Context mContext;
        private boolean bCaptureTexture = true;

        private Handler mInfoHandler;
        private RTCInfoListener mInfoListener;
        private OneRTCCallbacks mOneCallbacks;


        public OneRTCImplement(Looper mLooper,Context mContext,
                               OneRTCCallbacks mCallbacks,Handler mCallbackHandler) {
            mUIHandler = new Handler(Looper.getMainLooper());
            iceConnected = false;
            signalingParameters = null;
            this.mContext = mContext;
            this.mOneCallbacks = mCallbacks;

            // Create UI controls.
            pipRenderer = new SurfaceEglRendererNew("pip");
            fullscreenRenderer = new SurfaceEglRendererNew("full");

            remoteRenderers.add(remoteProxyRenderer);

            // Create peer connection client.
            peerConnectionClient = new PeerConnectionClient();

            // Start with local feed in fullscreen and swap it to the pip when the call is connected.
            setSwappedFeeds(true /* isSwappedFeeds */);

            // Check for mandatory permissions.
            for (String permission : MANDATORY_PERMISSIONS) {
                if (mContext.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG,"Permission " + permission + " mRtcInitParamis not granted");
                    return;
                }
            }

            RTCInitParam mRtcInitParam  = new RTCInitParam();

            peerConnectionParameters =
                    new PeerConnectionClient.PeerConnectionParameters(
                            mRtcInitParam.videoCallEnabled,
                            mRtcInitParam.loopback,
                            mRtcInitParam.tracing,
                            mRtcInitParam.videoWidth,
                            mRtcInitParam.videoHeight,
                            mRtcInitParam.videoFps,
                            mRtcInitParam.videoMaxBitrate,
                            mRtcInitParam.videoCodec,
                            mRtcInitParam.videoCodecHwAcceleration,
                            mRtcInitParam.videoFlexfecEnabled,
                            mRtcInitParam.audioStartBitrate,
                            mRtcInitParam.audioCodec,
                            mRtcInitParam.noAudioProcessing,
                            mRtcInitParam.aecDump,
                            mRtcInitParam.useOpenSLES,
                            mRtcInitParam.disableBuiltInAEC,
                            mRtcInitParam.disableBuiltInAGC,
                            mRtcInitParam.disableBuiltInNS,
                            mRtcInitParam.enableLevelControl,
                            mRtcInitParam.disableWebRtcAGCAndHPF,
                            mRtcInitParam.dataChannelParameters);

            // Create CPU monitor
            if (cpuMonitor.isSupported()) {
                cpuMonitor = new CpuMonitor(mContext);
            }

            peerConnectionClient.createPeerConnectionFactory(
                    mContext, peerConnectionParameters, OneRTCImplement.this);

            mHandler = new Handler(mLooper);
            if(mCallbackHandler != null)
            {
                mCBHandler = mCallbackHandler;
            }
            else
            {
                mCBHandler = mHandler;
            }

            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    pipRenderer.init(peerConnectionClient.getRenderContext(), EglBase.CONFIG_PLAIN, new GlRectDrawer());
//            pipRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);

                    fullscreenRenderer.init(peerConnectionClient.getRenderContext(),  EglBase.CONFIG_PLAIN, new GlRectDrawer());
//            fullscreenRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);

                }
            });
            // Create video renderers.
        }

        public void setInfoUpdateListener(Handler mHandler,RTCInfoListener mInfoListener)
        {
            this.mInfoHandler = mHandler;
            this.mInfoListener = mInfoListener;
        }

        public void setSurface(Surface mFullSurface, Surface mPipSurface)
        {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    pipRenderer.createEglSurface(mPipSurface);
                    fullscreenRenderer.createEglSurface(mFullSurface);
                }
            });
        }

        private boolean useCamera2() {
            return Camera2Enumerator.isSupported(mContext);
        }

        private boolean captureToTexture() {
            return bCaptureTexture;
        }

        private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
            final String[] deviceNames = enumerator.getDeviceNames();

            // First, try to find front facing camera
            Logging.d(TAG, "Looking for front facing cameras.");
            for (String deviceName : deviceNames) {
                if (enumerator.isFrontFacing(deviceName)) {
                    Logging.d(TAG, "Creating front facing camera capturer.");
                    VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);

                    if (videoCapturer != null) {
                        return videoCapturer;
                    }
                }
            }

            // Front facing camera not found, try something else
            Logging.d(TAG, "Looking for other cameras.");
            for (String deviceName : deviceNames) {
                if (!enumerator.isFrontFacing(deviceName)) {
                    Logging.d(TAG, "Creating other camera capturer.");
                    VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);

                    if (videoCapturer != null) {
                        return videoCapturer;
                    }
                }
            }

            return null;
        }

        @TargetApi(21)
        private VideoCapturer createScreenCapturer() {
            if (mediaProjectionPermissionResultCode != Activity.RESULT_OK) {
                reportError("User didn't give permission to capture the screen.");
                return null;
            }
            return new ScreenCapturerAndroid(
                    mediaProjectionPermissionResultData, new MediaProjection.Callback() {
                @Override
                public void onStop() {
                    reportError("User revoked permission to capture the screen.");
                }
            });
        }

        public void stop() {
            // Don't stop the video when using screencapture to allow user to show other apps to the remote
            // end.
            if (peerConnectionClient != null && !screencaptureEnabled) {
                peerConnectionClient.stopVideoSource();
            }
            if (cpuMonitor != null) {
                cpuMonitor.pause();
            }
        }

        public void resume() {
            // Video is not paused for screencapture. See onPause.
            if (peerConnectionClient != null && !screencaptureEnabled) {
                peerConnectionClient.startVideoSource();
            }
            if (cpuMonitor != null) {
                cpuMonitor.resume();
            }
        }

        public void switchCamera() {
            if (peerConnectionClient != null) {
                peerConnectionClient.switchCamera();
            }
        }

        public void changeCaptureFormat(int width, int height, int framerate) {
            if (peerConnectionClient != null) {
                peerConnectionClient.changeCaptureFormat(width, height, framerate);
            }
        }

        public void setMixEnabled(boolean bState) {
            if (peerConnectionClient != null) {
                peerConnectionClient.setAudioEnabled(bState);
            }
        }

        private LinkedList<PeerConnection.IceServer> requestTurnServers()
        {
            LinkedList<PeerConnection.IceServer> turnServers = new LinkedList<PeerConnection.IceServer>();
            LinkedList<TurnServerInfo> mTurnList = new LinkedList<>();

            mTurnList.add(new TurnServerInfo("turn:coturn-sz.insta360.cn","insta360","50lan123"));
            mTurnList.add(new TurnServerInfo("turn:coturn-uw.insta360.cn","insta360","50lan123"));
            mTurnList.add(new TurnServerInfo("turn:coturn-hk.insta360.cn","insta360","50lan123"));

            for(int i = 0; i < mTurnList.size(); i++)
            {
                PeerConnection.IceServer turnServer =
                        PeerConnection.IceServer.builder(mTurnList.get(i).url)
                                .setUsername(mTurnList.get(i).username)
                                .setPassword(mTurnList.get(i).credential)
                                .createIceServer();
                turnServers.add(turnServer);
            }

            return turnServers;
        }

        private void checkStateIdle()
        {
            if(mRTCState != RTCState.IDLE)
            {
                throw new IllegalStateException("rtc not idle mRTCState " + mRTCState);
            }
        }

        private void checkStateConnect()
        {
            if(mRTCState != RTCState.CONNECTED)
            {
                throw new IllegalStateException("rtc not connect mRTCState " + mRTCState);
            }
        }

        private void setState(RTCState mState)
        {
            mRTCState = mState;
        }

        public void connect(final RTCConnectInfo mConnectInfo)
        {
            Log.i(TAG,"connect");
            checkStateIdle();
            appRtcClient = new WebSocketRTCClientNew(this);
            callStartedTimeMs = System.currentTimeMillis();

            Log.d(TAG,"mConnectInfo.roomId " + mConnectInfo.roomId);
            appRtcClient.connectToSignalServer(mConnectInfo);
            // Create and audio manager that will take care of audio routing,
            // audio modes, audio device enumeration etc.
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    audioManager = AppRTCAudioManager.create(mContext);
                    // Store existing audio settings and change audio mode to
                    // MODE_IN_COMMUNICATION for best possible VoIP performance.
                    Log.d(TAG, "Starting the audio manager...");
                    audioManager.start(new AppRTCAudioManager.AudioManagerEvents() {
                        // This method will be called each time the number of available audio
                        // devices has changed.
                        @Override
                        public void onAudioDeviceChanged(
                                AppRTCAudioManager.AudioDevice audioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
                            onAudioManagerDevicesChanged(audioDevice, availableAudioDevices);
                        }
                    });
                }
            });
            setState(RTCState.CONNECTED);
        }

        public void release()
        {

        }

        private void setSwappedFeeds(boolean bState)
        {
            isSwappedFeeds = bState;
            localProxyVideoSink.setTarget(bState ? fullscreenRenderer : pipRenderer);
            remoteProxyRenderer.setTarget(bState ? pipRenderer : fullscreenRenderer);
            fullscreenRenderer.setMirror(bState);
            pipRenderer.setMirror(!bState);
        }

        // Should be called from UI thread
        private void callConnected() {
            final long delta = System.currentTimeMillis() - callStartedTimeMs;
            Log.i(TAG, "Call connected: delay=" + delta + "ms");
            if (peerConnectionClient == null || isError) {
                Log.w(TAG, "Call is connected in closed or error state");
                return;
            }
            // Enable statistics callback.
            peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD);
            setSwappedFeeds(false /* isSwappedFeeds */);
        }

        // This method is called when the audio manager reports audio device change,
        // e.g. from wired headset to speakerphone.
        private void onAudioManagerDevicesChanged(
                final AppRTCAudioManager.AudioDevice device, final Set<AppRTCAudioManager.AudioDevice> availableDevices) {
            Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
                    + "selected: " + device);
            // TODO(henrika): add callback handler.
        }

        // Disconnect from remote resources, dispose of local resources, and exit.
        public void disconnect() {
            Log.i(TAG,"disconnect");
            checkStateConnect();
            remoteProxyRenderer.setTarget(null);
            localProxyVideoSink.setTarget(null);
            if (appRtcClient != null) {
                appRtcClient.disconnectFromRoom();
                appRtcClient = null;
            }
            if (pipRenderer != null) {
                pipRenderer.release();
//                pipRenderer = null;
            }
//            if (videoFileRenderer != null) {
//                videoFileRenderer.release();
//                videoFileRenderer = null;
//            }
            if (fullscreenRenderer != null) {
                fullscreenRenderer.release();
//                fullscreenRenderer = null;
            }
            if (peerConnectionClient != null) {
                peerConnectionClient.close();
                peerConnectionClient = null;
            }
            if (audioManager != null) {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        audioManager.stop();
                        audioManager = null;
                    }
                });
            }
            if (iceConnected && !isError)
            {
            }
            else
            {

            }

            setState(RTCState.IDLE);
        }

        private void disconnectWithErrorMessage(final String errorMessage) {
            Log.e(TAG, "Critical error: " + errorMessage);
            onDisconnect(errorMessage);
        }

        private void reportError(final String description) {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    if (!isError) {
                        isError = true;
                        disconnectWithErrorMessage(description);
                    }
                }
            });
        }

        private VideoCapturer createVideoCapturer() {
            final VideoCapturer videoCapturer;
            String videoFileAsCamera = null;
            if (videoFileAsCamera != null) {
                try {
                    videoCapturer = new FileVideoCapturer(videoFileAsCamera);
                } catch (IOException e) {
                    reportError("Failed to open video file for emulated camera");
                    return null;
                }
            } else if (screencaptureEnabled) {
                return createScreenCapturer();
            } else if (useCamera2()) {
                Logging.d(TAG, "Creating capturer using camera2 API.");
                videoCapturer = createCameraCapturer(new Camera2Enumerator(mContext));
            } else {
                Logging.d(TAG, "Creating capturer using camera1 API.");
                videoCapturer = createCameraCapturer(new Camera1Enumerator(captureToTexture()));
            }
            if (videoCapturer == null) {
                reportError("Failed to open camera");
                return null;
            }
            return videoCapturer;
        }

        public void switchFeeds() {
            Logging.d(TAG, "switchFeeds: " + isSwappedFeeds);
            setSwappedFeeds(!isSwappedFeeds);
        }

        private void onConnectedSignalServerInternal(final ConnectSignalServerInfo params) {
            final long delta = System.currentTimeMillis() - callStartedTimeMs;

            Log.d(TAG,"onConnectedSignalServerInternal delay=" + delta + "ms");
            VideoCapturer videoCapturer = null;
            if (peerConnectionParameters.videoCallEnabled) {
                videoCapturer = createVideoCapturer();
            }

            signalingParameters = params.signalingParameters;
            peerConnectionClient.createPeerConnection(
                    localProxyVideoSink, remoteRenderers, videoCapturer, signalingParameters);

            if (signalingParameters.initiator) {
                Log.d(TAG,"Creating OFFER...");
                // Create offer. Offer SDP will be sent to answering client in
                // PeerConnectionEvents.onLocalDescription event.
                peerConnectionClient.createOffer();
            } else {
//      if (params.offerSdp != null) {
//        peerConnectionClient.setRemoteDescription(params.offerSdp);
//        Log.d(TAG,"Creating ANSWER...");
//        // Create answer. Answer SDP will be sent to offering client in
//        // PeerConnectionEvents.onLocalDescription event.
//        peerConnectionClient.createAnswer();
//      }
//      if (signalingParameters.iceCandidates != null) {
//        // Add remote ICE candidates from room.
//        for (IceCandidate iceCandidate : signalingParameters.iceCandidates) {
//          peerConnectionClient.addRemoteIceCandidate(iceCandidate);
//        }
//      }
            }
        }

        // -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
        // All callbacks are invoked from websocket signaling looper thread and
        // are routed to UI thread.
        private void onConnectedToRoomInternal(final AppRTCClient.SignalingParameters params) {
            final long delta = System.currentTimeMillis() - callStartedTimeMs;

            signalingParameters = params;
            Log.d(TAG,"Creating peer connection, delay=" + delta + "ms");
            VideoCapturer videoCapturer = null;
            if (peerConnectionParameters.videoCallEnabled) {
                videoCapturer = createVideoCapturer();
            }
            peerConnectionClient.createPeerConnection(
                    localProxyVideoSink, remoteRenderers, videoCapturer, signalingParameters);

            if (signalingParameters.initiator) {
                Log.d(TAG,"Creating OFFER...");
                // Create offer. Offer SDP will be sent to answering client in
                // PeerConnectionEvents.onLocalDescription event.
                peerConnectionClient.createOffer();
            } else {
                if (params.offerSdp != null) {
                    peerConnectionClient.setRemoteDescription(params.offerSdp);
                    Log.d(TAG,"Creating ANSWER...");
                    // Create answer. Answer SDP will be sent to offering client in
                    // PeerConnectionEvents.onLocalDescription event.
                    peerConnectionClient.createAnswer();
                }
                if (params.iceCandidates != null) {
                    // Add remote ICE candidates from room.
                    for (IceCandidate iceCandidate : params.iceCandidates) {
                        peerConnectionClient.addRemoteIceCandidate(iceCandidate);
                    }
                }
            }
        }

    private void runOnMainThread(final Runnable runnable) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            runnable.run();
        } else {
            mUIHandler.post(runnable);
        }
    }

        @Override
        public void onConnectedSignalServer(final ConnectSignalServerInfo params)
        {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    onConnectedSignalServerInternal(params);
                }
            });
        }

        @Override
        public void onRemoteDescription(final SessionDescription sdp) {
            final long delta = System.currentTimeMillis() - callStartedTimeMs;
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    if (peerConnectionClient == null) {
                        Log.e(TAG, "Received remote SDP for non-initilized peer connection.");
                        return;
                    }
                    Log.d(TAG,"Received remote " + sdp.type + ", delay=" + delta + "ms");
                    peerConnectionClient.setRemoteDescription(sdp);
//        if (!signalingParameters.initiator) {
                    if (sdp.type.equals(SessionDescription.Type.OFFER)) {
                        Log.d(TAG,"new Creating ANSWER...");
                        // Create answer. Answer SDP will be sent to offering client in
                        // PeerConnectionEvents.onLocalDescription event.
                        peerConnectionClient.createAnswer();
                    }
                }
            });
        }

        @Override
        public void onRemoteIceCandidate(final IceCandidate candidate) {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    if (peerConnectionClient == null) {
                        Log.e(TAG, "Received ICE candidate for a non-initialized peer connection.");
                        return;
                    }
                    Log.d(TAG,"onRemoteIceCandidate");
                    peerConnectionClient.addRemoteIceCandidate(candidate);
                }
            });
        }

        @Override
        public void onRemoteIceCandidatesRemoved(final IceCandidate[] candidates) {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    if (peerConnectionClient == null) {
                        Log.e(TAG, "Received ICE candidate removals for a non-initialized peer connection.");
                        return;
                    }
                    peerConnectionClient.removeRemoteIceCandidates(candidates);
                }
            });
        }

        private void onDisconnect(String disconnectError)
        {
            mOneCallbacks.onDisconnect(disconnectError);
        }

        @Override
        public void onChannelClose() {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG,"Remote end hung up; dropping PeerConnection");
                    onDisconnect(OneRTCConstants.ERROR.CHANEL_CLOSE);
                }
            });
        }

        @Override
        public void onChannelError(final String description) {
            reportError(description);
        }

        // -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
        // Send local peer connection SDP and ICE candidates to remote party.
        // All callbacks are invoked from peer connection client looper thread and
        // are routed to UI thread.
        @Override
        public void onLocalDescription(final SessionDescription sdp) {
            final long delta = System.currentTimeMillis() - callStartedTimeMs;
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    if (appRtcClient != null) {
                        Log.d(TAG,"Sending " + sdp.type + ", delay=" + delta + "ms" +
                                " signalingParameters.initiator " +
                                signalingParameters.initiator);
                        if (signalingParameters.initiator) {
                            appRtcClient.sendOfferSdp(sdp);
                        } else {
                            appRtcClient.sendAnswerSdp(sdp);
                        }
                    }
                    if (peerConnectionParameters.videoMaxBitrate > 0) {
                        Log.d(TAG, "Set video maximum bitrate: " + peerConnectionParameters.videoMaxBitrate);
                        peerConnectionClient.setVideoMaxBitrate(peerConnectionParameters.videoMaxBitrate);
                    }
                }
            });
        }

        @Override
        public void onIceCandidate(final IceCandidate candidate) {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    if (appRtcClient != null) {
                        appRtcClient.sendLocalIceCandidate(candidate);
                    }
                }
            });
        }

        @Override
        public void onIceCandidatesRemoved(final IceCandidate[] candidates) {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    if (appRtcClient != null) {
                        appRtcClient.sendLocalIceCandidateRemovals(candidates);
                    }
                }
            });
        }

        @Override
        public void onIceConnected() {
            final long delta = System.currentTimeMillis() - callStartedTimeMs;
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG,"ICE connected, delay=" + delta + "ms");
                    iceConnected = true;
                    callConnected();
                }
            });
        }

        @Override
        public void onIceDisconnected() {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG,"ICE disconnected");
                    iceConnected = false;
                    onDisconnect(OneRTCConstants.ERROR.ICE_DISCONNECT);
                }
            });
        }

        @Override
        public void onPeerConnectionClosed() {}

        @Override
        public void onPeerConnectionStatsReady(final StatsReport[] reports) {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    if (!isError && iceConnected) {
//                        hudFragment.updateEncoderStatistics(reports);
                    }
                }
            });
        }

        @Override
        public void onPeerConnectionError(final String description) {
            reportError(description);
        }
}
